/*
 * Copyright (c) 2020, nwillc@gmail.com
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package com.gitee.wsl.common.svg.elements

import com.gitee.wsl.api.Cloneable
import com.gitee.wsl.common.svg.attributes.AttributeProperty
import com.gitee.wsl.common.svg.attributes.AttributeType
import com.gitee.wsl.transform.format.Format
import kotlin.jvm.JvmOverloads


/**
 * An SVG path element.
 */
class PATH(validation: Boolean = false) : Region("path", validation) {
    /**
     * The path definition.
     */
    var d: String? by AttributeProperty(type = AttributeType.Path)
}


/**
 * The general path shape
 * It use some command line to generate path
 * Here is the command
 * <pre>
 * M = moveto
 * L = lineto
 * H = horizontal lineto
 * V = vertical lineto
 * C = curveto
 * S = smooth curveto
 * Q = quadratic Belzier curve
 * T = smooth quadratic Belzier curveto
 * A = elliptical Arc
 * Z = closepath
</pre> *
 * It will generate svg element like this
 * <pre>
 * <path d="M10 10 H 90 V 90 H 10 Z"></path>
</pre> *
 * if you want to know about the SVG path command,plz refer to http://www.verydoc.net/svg/00007415.html
 *
 * @author iffly
 * @since 0.0.1
 */
class SVGPath {

    /**
     * Get path command string
     *
     * @param convert The double convert
     * @return The path command string
     * @since 0.0.1
     */
   fun getSVGPathD(convert: Format<Double> = Format{ value -> value.toString() }): String {
        val iterator = iterator()
        val sb = StringBuilder()
        while (iterator.hasNext()) {
            val pathElement = iterator.next()
            sb.append(pathElement.valueStr(convert))
        }
        return sb.toString()
    }

    /**
     * The path command class
     * Here is the command
     * <pre>
     * M = moveto
     * L = lineto
     * H = horizontal lineto
     * V = vertical lineto
     * C = curveto
     * S = smooth curveto
     * Q = quadratic Belzier curve
     * T = smooth quadratic Belzier curveto
     * A = elliptical Arc
     * Z = closepath
    </pre> *
     *
     * @author iffly
     * @since 0.0.1
     */
    class SVGPathElement @JvmOverloads constructor(
        /**
         * Command type
         */
         var type: String,
        /**
         * Pos data
         */
        var data: FloatArray?,
        /**
         * Indicates whether the relative distance
         * true mean that is relative
         */
        private val isRelative: Boolean = false
    ) {

        object PathElementType {
                /**
                 * Path command
                 */
                const val  M = "M"
                const val  L = "L"
                const val  H = "H"
                const val  V = "V"
                const val  C = "C"
                const val  S = "S"
                const val  Q = "Q"
                const val  T = "T"
                const val  A = "A"
                const val  Z = "Z"

        }


        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (other == null || this::class != other::class) return false
            val that = other as SVGPathElement
            return type == that.type && data.contentEquals(that.data)
        }

        override fun hashCode(): Int {
            var result = type.hashCode()
            result = 31 * result + data.hashCode()
            return result
        }

        /**
         * Get path command string
         *
         * @param convert The double convert,convert double to string
         * @return The path command string
         */
        fun valueStr(convert: Format<Double>): String {
            val stringBuilder = StringBuilder()
            stringBuilder.append(if (isRelative) type.lowercase() else type).append(" ")
            if (type !== PathElementType.A) {
                if (data != null)
                    for (f in data!!)
                        stringBuilder.append(convert(f.toDouble())).append(" ")
            } else {
                if (data != null) for (i in data!!.indices) {
                    if (i == 3 || i == 4) {
                        stringBuilder.append(data!![i].toInt()).append(" ")
                    } else
                        stringBuilder.append(data!![i]).append(" ")
                }
            }
            return stringBuilder.toString()
        }
    }

    /**
     * Path commands
     * Use it to save ptah commands
     *
     * @see SVGPathElement
     */
    private val pathElements: MutableList<SVGPathElement> = ArrayList()

    constructor() {}
    constructor(path: SVGPath) {
        pathElements.addAll(path.pathElements)
    }
    /**
     * Move point to (x,y)/(x0+x,y0+y)(if isRelative is true)
     *
     * @param x
     * @param y
     * @param isRelative Indicates whether the relative distance,true means that relative
     * @since 0.0.1
     */
    /**
     * Move point to (x,y)
     *
     * @param x
     * @param y
     * @since 0.0.1
     */
    @JvmOverloads
    fun moveTo(x: Float, y: Float, isRelative: Boolean = false) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.M,
                floatArrayOf(x, y),
                isRelative
            )
        )
    }
    /**
     * draw line to (x,y)/(x0+x,y0+y)(if isRelative is true)
     *
     * @param x
     * @param y
     * @param isRelative Indicates whether the relative distance,true means that relative
     * @since 0.0.1
     */
    /**
     * draw line to (x,y)
     *
     * @param x
     * @param y
     * @since 0.0.1
     */
    @JvmOverloads
    fun lineTo(x: Float, y: Float, isRelative: Boolean = false) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.L,
                floatArrayOf(x, y),
                isRelative
            )
        )
    }
    /**
     * draw hor line to(x,y0)/(x0+x,y0)(if isRelative is true)
     *
     * @param x
     * @param isRelative Indicates whether the relative distance,true means that relative
     * @since 0.0.1
     */
    /**
     * draw hor line to(x,y0)
     *
     * @param x
     * @since 0.0.1
     */
    @JvmOverloads
    fun horizontalLineTo(x: Float, isRelative: Boolean = false) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.H,
                floatArrayOf(x),
                isRelative
            )
        )
    }
    /**
     * draw ver line to(x0,y)/(x0,y0+y)(if isRelative is true)
     *
     * @param y
     * @param isRelative Indicates whether the relative distance,true means that relative
     * @since 0.0.1
     */
    /**
     * draw ver line to(x0,y)
     *
     * @param y
     * @since 0.0.1
     */
    @JvmOverloads
    fun verticalLineTo(y: Float, isRelative: Boolean = false) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.V,
                floatArrayOf(y),
                isRelative
            )
        )
    }

    /**
     * curveTo (ex,ey)
     * Cubic Bezier curve
     *
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param ex
     * @param ey
     * @since 0.0.1
     */
    @JvmOverloads
    fun curveTo(
        x1: Float,
        y1: Float,
        x2: Float,
        y2: Float,
        ex: Float,
        ey: Float,
        isRelative: Boolean = false
    ) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.C, floatArrayOf(x1, y1, x2, y2, ex, ey),
                isRelative
            )
        )
    }

    /**
     * curveTo (ex,ey)
     * When multiple cubic Bezier curves are connected, the control points of the previous curve will be used to smoothly connect
     * Some times we use like this
     * <pre>
     * SVGPath path = new SVGPath();
     * path.moveTo(10,10);
     * path.curveTo(15,13,18,20,10,30);
     * path.smoothCurveTo(20,30,10,40);
    </pre> *
     *
     * @param x2
     * @param y2
     * @param ex
     * @param ey
     * @since 0.0.1
     */
    @JvmOverloads
    fun smoothCurveTo(x2: Float, y2: Float, ex: Float, ey: Float, isRelative: Boolean = false) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.C, floatArrayOf(x2, y2, ex, ey),
                isRelative
            )
        )
    }

    /**
     * curveTo(ex,ey)
     * Quadratic bezier curve
     *
     * @param x1
     * @param y1
     * @param ex
     * @param ey
     * @since 0.0.1
     */
    @JvmOverloads
    fun quadraticBelzierCurve(
        x1: Float,
        y1: Float,
        ex: Float,
        ey: Float,
        isRelative: Boolean = false
    ) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.Q, floatArrayOf(x1, y1, ex, ey),
                isRelative
            )
        )
    }

    /**
     * curveTo(x,y)
     * When connecting multiple quadratic Bezier curves, use the control points of the previous curve to smoothly connect
     *
     * @param x
     * @param y
     * @since 0.0.1
     */
    @JvmOverloads
    fun smoothQuadraticBelzierCurve(x: Float, y: Float, isRelative: Boolean = false) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.T,
                floatArrayOf(x, y),
                isRelative
            )
        )
    }

    /**
     * arcTo(x,y)
     *
     * @param rx
     * @param ry
     * @param rotation  The rotation of axis-x
     * @param isLarge   1 means that large arc
     * @param sweepFlag 1 means that arcTo with clockwise
     * @param x
     * @param y
     * @since 0.0.1
     */
    @JvmOverloads
    fun ellipticalArc(
        rx: Float,
        ry: Float,
        rotation: Float,
        isLarge: Int,
        sweepFlag: Int,
        x: Float,
        y: Float,
        isRelative: Boolean = false
    ) {
        pathElements.add(
            SVGPathElement(
                SVGPathElement.PathElementType.A,
                floatArrayOf(rx, ry, rotation, isLarge.toFloat(), sweepFlag.toFloat(), x, y),
                isRelative
            )
        )
    }

    /**
     * draw oval
     * Conveniently draw ellipse,it will moveTo(x-rx,y)
     *
     * @param x
     * @param y
     * @param rx
     * @param ry
     * @since 0.0.1
     */
    fun oval(x: Float, y: Float, rx: Float, ry: Float) {
        moveTo(x - rx, y)
        ellipticalArc(rx, ry, 0f, 1, 0, x + rx, y)
        ellipticalArc(rx, ry, 0f, 1, 0, x - rx, y)
    }

    /**
     * draw a rect
     * Conveniently draw rectangles,it will not move to(x,y)
     *
     * @param x
     * @param y
     * @param width
     * @param height
     * @since 0.0.1
     */
    fun rect(x: Float, y: Float, width: Float, height: Float) {
        moveTo(x, y)
        horizontalLineTo(width, true)
        verticalLineTo(height, true)
        horizontalLineTo(-width, true)
        verticalLineTo(-height, true)
    }

    /**
     * Close path
     * Connect start and end
     *
     * @since 0.0.1
     */
    fun closePath() {
        pathElements.add(SVGPathElement(SVGPathElement.PathElementType.Z, null))
    }

    /**
     * The path command iterator
     *
     * @return
     * @since 0.0.1
     */
    operator fun iterator(): Iterator<SVGPathElement> {
        return pathElements.listIterator()
    }

    /*public override fun clone(): Any {
        return SVGPath(this)
    }*/

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null || this::class != other::class) return false
        val path = other as SVGPath
        return pathElements == path.pathElements
    }

    override fun hashCode(): Int {
        return pathElements.hashCode()
    }
}